Added Push Camera#15151
Conversation
|
I have been told not cache image in camera, Maybe better use Better make |
|
Little notice: Seems like the docs are missing. |
| async def update_image(self, image, filename): | ||
| """Update the camera image.""" | ||
| if self._state == STATE_IDLE: | ||
| self._last_trip = dt_util.utcnow() |
There was a problem hiding this comment.
This seems to be the same as last_changed state attribute. If that's true, why do we need this?
There was a problem hiding this comment.
It is not, this will refer to the moment we start recording, last_changed will be updated during recording. The duration of the recording will be last_changed - last_trip
There was a problem hiding this comment.
If state is the same, ie recording, last_changed will not be updated after a state update.
There was a problem hiding this comment.
last_trip is only set on the transition STATE_IDLE -> STATE_RECORDING
last_changed is set on both transitions
both attributes will be equal only during the STATE_RECORDING state.
There was a problem hiding this comment.
Ok, thanks for clarifying.
| self._expired = async_track_point_in_utc_time( | ||
| self.hass, reset_state, dt_util.utcnow() + self._timeout) | ||
|
|
||
| self.schedule_update_ha_state() |
There was a problem hiding this comment.
Use the async version here since we're in a coroutine.
|
|
||
| self.schedule_update_ha_state() | ||
|
|
||
| def camera_image(self): |
There was a problem hiding this comment.
This isn't needed since we're overwriting the async version of this method below.
| try: | ||
| data = await request.post() | ||
| _LOGGER.debug("Received Camera push: %s", data['image']) | ||
| await _camera.update_image(data['image'].file.read(), |
There was a problem hiding this comment.
Is this api to POST an image standardized or is it specific for this implementation?
There was a problem hiding this comment.
It is not standardised, I will make it configurable as suggested by @awarecan
| @property | ||
| def motion_detection_enabled(self): | ||
| """Camera Motion Detection Status.""" | ||
| return self._motion_status |
There was a problem hiding this comment.
If this is static, we can just return False here and don't need the instance attribute.
| self._filename = None | ||
| self._expired = None | ||
| self._state = STATE_IDLE | ||
| self._timeout = datetime.timedelta(seconds=timeout) |
There was a problem hiding this comment.
Use the timedelta config validation helper in the schema instead.
| async def async_setup_platform(hass, config, async_add_devices, | ||
| discovery_info=None): | ||
| """Set up the Push Camera platform.""" | ||
| cameras = [PushCamera(config.get(CONF_NAME), |
There was a problem hiding this comment.
dict.get isn't needed since there are defaults in place via the config schema.
|
@awarecan tks for your comment, per default there is no cache only the current image. But for the described use case (motioneye) makes much sense to cache the event. So lets see what others think about it. Good point on making |
|
|
||
| from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\ | ||
| STATE_IDLE, STATE_RECORDING | ||
| from homeassistant.util.async_ import run_coroutine_threadsafe |
There was a problem hiding this comment.
'homeassistant.util.async_.run_coroutine_threadsafe' imported but unused
| resp = await client.post('/api/camera_push/camera.wrong', data=files) | ||
| assert resp.status == 400 | ||
|
|
||
| @mock.patch("PIL.Image.new") |
| from homeassistant.util import dt as dt_util | ||
| from tests.components.auth import async_setup_auth | ||
|
|
||
| @mock.patch("PIL.Image.new") |
| return self.json_message('Invalid POST', HTTP_BAD_REQUEST) | ||
| except KeyError as key_error: | ||
| _LOGGER.error('In your POST message %s', key_error) | ||
| return self.json_message('Parameter {} missing'.format(self._image), HTTP_BAD_REQUEST) |
There was a problem hiding this comment.
line too long (98 > 79 characters)
| return self.json_message('Invalid POST', HTTP_BAD_REQUEST) | ||
| except KeyError as key_error: | ||
| _LOGGER.error('In your POST message %s', key_error) | ||
| return self.json_message('Parameter {} missing'.format(self._image), |
There was a problem hiding this comment.
line too long (80 > 79 characters)
pvizeli
left a comment
There was a problem hiding this comment.
Looks good. But we should work like the other cameras and if there is no picutre, we return none.
|
Returning none will impact the frontend, if the picture takes to long to appear the browser shows a missing image and not the proposed blank (more user friendly) Either this logic (create a blank image) moves to the parent class, or the frontend should better handle the None image. Opinions ? |
|
Frontend should handle it better, period. No error handling in platforms for things related to the frontend. |
|
|
||
| self._state = STATE_RECORDING | ||
| self._filename = filename | ||
| self.queue.append(image) |
There was a problem hiding this comment.
I feel like this platform is getting over complicated. It should just show the last pushed image.
We should also store that image on disk and not in memory.
There was a problem hiding this comment.
With que, you could see a stream if you push multible images into the entity. Maybe default 1 and you can overwrite this with a option?
There was a problem hiding this comment.
This is abusing a feature of our camera integration. The camera get_image function should show a realtime stream of what a camera is showing.
We don't support replays in our camera integration right now. So we shouldn't hack it in.
There was a problem hiding this comment.
I feel strongly against writing to disk. Most people run HA on an SD card, it's really bad to write continuously to the SD.
I share @pvizeli comment (that was my intention in the implementation, therefore the current default size of deque = 1).
Let me remind you that the most common use case for this camera is not realtime, and most of the time the user will be faced with the last image (very poor context on what happened).
There was a problem hiding this comment.
It could be resolved by allow customized save image path, then point it to some tmpfs.
There was a problem hiding this comment.
Let me remind you that the most common use case for this camera is not realtime
I don't like this assumption. If you think you need a well-tuned components only for your use case, probably should not use a such generic name.
There was a problem hiding this comment.
@awarecan sure it can (thats how I did before developing this code)
But that's not easy for the average user, and would require hassio/hassos to be aware of that need.
We are not discussing full length video, we are discussing 3 or 5 images (it doesn't take that much memory)
There was a problem hiding this comment.
@awarecan I first had this component living in my custom_components repo (name is http_post). But realised people were using it for the described use case.
There is nothing specific in the component related to motionEye, so I did not call it camera.motionEye and even made some changes to make it even more general.
There was a problem hiding this comment.
A Raspberry Pi has 1GB of RAM. If 10 images are kept around, 1MB each, we'll be using 1% of memory for this feature.
|
Camera Push will now return image None before first image is received. |
| if self._expired: | ||
| self._expired() | ||
|
|
||
| self._expired = async_track_point_in_utc_time( |
There was a problem hiding this comment.
rename this like _expired_listener so that you know what it is
| """Set state to idle after no new images for a period of time.""" | ||
| self._state = STATE_IDLE | ||
| self.async_schedule_update_ha_state() | ||
| self._expired = None |
There was a problem hiding this comment.
first set all variable and after that call the function
| self._last_trip = dt_util.utcnow() | ||
| self.queue.clear() | ||
|
|
||
| self._filename = filename |
There was a problem hiding this comment.
do we really need this? That make a state change any new picture is pushed
There was a problem hiding this comment.
That is actually a feature :)
That state change will help automations that require a trigger (e.g. do some image_processing, send a notification).
There was a problem hiding this comment.
Yeah, that will be trigger if the state of camera going to recording, why you need after that a state change?
There was a problem hiding this comment.
In the use case of motioneye, an event will consist of 3 images (default)
I expect to be notified of the 3 for the aforementioned automations.
There was a problem hiding this comment.
I have a idea. Add the options force_update (default false) and use this on entity as attribute force_update.
Other platform make the same. That allow user to force state change on if a update is exists. I think it is a bit wired to store a filename into attribute to have the same effect. What do you think?
There was a problem hiding this comment.
That is possible yes, I can work that out tonight.
But I also believe the filename can be a useful attribute by itself
There was a problem hiding this comment.
For what? Can you give a example why to you need this attribute?
There was a problem hiding this comment.
Filename is a unique key, and can be used to trace events.
Personal example:
MotionEye sends HA an image, HA sends a notification with the image. If I want to go back to motionEye image storage, I need a key to look for. Sending the filename with the notification lets me go back to motionEye storage and look for the image and review the security event (way past the time HA has cleared the image from the queue)
There was a problem hiding this comment.
Ok. Remove the force_update if you want staying with that attribute and we can merge it.
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
| vol.Optional(CONF_BUFFER_SIZE, default=1): cv.positive_int, |
There was a problem hiding this comment.
maybe queue_size ? I'm not sure if buffer_size to odd
There was a problem hiding this comment.
It's a buffer... that happens to be implemented with a queue.
I don't think it makes sense to rename it.
This reverts commit e203785.
|
I know this has already been merged, but it presently doesn't seem to actually allow pushing images to multiple defined entities, only the first one configured. All others throw a push camera not found error. The camera card and associated entities are properly created for all defined cameras you just can't post data to any one but the first. |
|
@mezz64 Tks for the bug report, but this is not the proper place :) I'm already issuing a new PR with a fix! |
Description:
Push Camera is a meta-platform that can receive images through the http API.
A use case, is the integration with motioneye. Motioneye is usually configured to save/record files ONLY when motion is detected. It provides a hook to run a command whenever an image is saved, which can be used together with cURL to send the motion detected images to Push Camera, as showed in this example:
curl -X POST -F "image=@%f" http://hassio.test:8123/api/camera_push/camera.my_push_cameraOptionally the Push Camera is able to buffer a given number of images, creating an animation of the detected motion.
Images are cleared on new events, and events are separated by a soft (configurable) timeout.
Related issue (if applicable): fixes #
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5606
Example entry for
configuration.yaml(if applicable):Checklist:
tox. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTSvariable (example).requirements_all.txtby runningscript/gen_requirements_all.py..coveragerc.If the code does not interact with devices: